package ants.map.scent;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

import ants.map.Coordinate;
import ants.map.CoordinateFactory;
import ants.map.GameMap;
import ants.map.tile.AntTile;

import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;

public class ScentedGameMap extends GameMap {

	private static final double DIFFUSION_COEFFICIENT = 0.23;
	private static final double SCENT_DAMPENER_COEFFICIENT = 0.80;

	private CoordinateFactory cf;

	private CombatScentMap[] combatScentMaps;
	private ScentMap[] scentMaps;

	public void diffuse() {
		Monitor mon = MonitorFactory.start("ScentedGameMap.diffuse");

		turnScentMapPages();

		Collection<Scent> diffusionScents = new ArrayList<Scent>(
				Scent.values().length);

		for (int row = 0; row < sizeRows; row++) {
			for (int col = 0; col < sizeCols; col++) {

				Monitor coordinateMonitor = MonitorFactory
						.start("ScentedGameMap.diffuse.coordinate");

				diffusionScents.clear();

				Coordinate c = cf.getCoordinate(row, col);
				if (isWater(c)) {
					for (Scent scent : Scent.values())
						scentMaps[scent.ordinal()].setScent(c, ScentMap.WATER);
					continue;
				}

				boolean myAnt = isMyAnt(c);

				/* set food source */
				if (isFood(c))
					scentMaps[Scent.Food.ordinal()].setScent(c, ScentMap.SOURCE);
				else if (myAnt)
					scentMaps[Scent.Food.ordinal()].setScent(c, ScentMap.NO_SCENT);
				else
					diffusionScents.add(Scent.Food);

				/* set hill source */
				if (isHillEnemy(c))
					scentMaps[Scent.Hill.ordinal()].setScent(c, ScentMap.SOURCE);
				else
					diffusionScents.add(Scent.Hill);

				/* set explore scent */
				if (myAnt)
					scentMaps[Scent.Explore.ordinal()].setScent(c, ScentMap.NO_SCENT);
				else if (!isSeen(c))
					scentMaps[Scent.Explore.ordinal()].setScent(c, ScentMap.SOURCE);
				else if (!isVisible(c))
					scentMaps[Scent.Explore.ordinal()].setScent(c, ScentMap.SOURCE_MINOR);
				else
					diffusionScents.add(Scent.Explore);

				/* set ant scents */
				if (isEnemyAnt(c)) {
					scentMaps[Scent.AntEnemy.ordinal()].setScent(c, ScentMap.SOURCE);
				} else if (isEnemyDeadAnt(c)) {
					scentMaps[Scent.AntEnemy.ordinal()].setScent(c, ScentMap.NO_SCENT);
				} else {
					diffusionScents.add(Scent.AntEnemy);
				}

				for (Scent scent : diffusionScents) {
					ScentMap scentMap = scentMaps[scent.ordinal()];
					diffuse(scentMap, c);
				}

				coordinateMonitor.stop();

			}
		}

		mon.stop();
	}

	private void diffuse(ScentMap scentMap, Coordinate c) {
		int previousScent = scentMap.getPreviousScent(c);
		int sumOfDifference = 0;

		for (Coordinate neighbor : getNeighbors(c)) {
			if (isWater(neighbor))
				continue;

			int neighborScent = scentMap.getPreviousScent(neighbor);
			sumOfDifference += (neighborScent - previousScent)
					* DIFFUSION_COEFFICIENT;
		}

		/* dissipate scent by adding dampener */
		{
			int dampener = (int) (SCENT_DAMPENER_COEFFICIENT * previousScent);
			sumOfDifference += (dampener - previousScent) * DIFFUSION_COEFFICIENT;
		}

		int newValue = previousScent + sumOfDifference;

		scentMap.setScent(c, newValue);
	}

	private void diffuseCombatScent() {

		for (CombatScent combatScent : CombatScent.values())
			combatScentMaps[combatScent.ordinal()].clear();

		for (AntTile ant : getAllAnts()) {
			diffuseCombatScent(ant);
		}

	}

	private void diffuseCombatScent(AntTile ant) {

		CombatScentMap scentMapAlert;
		CombatScentMap scentMapDanger;

		if (isMyAnt(ant)) {
			scentMapAlert = combatScentMaps[CombatScent.FriendlyAlert.ordinal()];
			scentMapDanger = combatScentMaps[CombatScent.FriendlyDanger.ordinal()];
		} else {
			scentMapAlert = combatScentMaps[CombatScent.EnemyAlert.ordinal()];
			scentMapDanger = combatScentMaps[CombatScent.EnemyDanger.ordinal()];
		}

		Collection<Coordinate> alertZone = getAlertZone(ant);
		Collection<Coordinate> dangerZone = getDangerZone(ant);

		Collection<Coordinate> filteredAlertZone = new HashSet<Coordinate>();
		Collection<Coordinate> filteredDangerZone = new HashSet<Coordinate>();

		int alertZoneThreat = 0;
		int dangerZoneThreat = 0;

		Queue<Coordinate> unsettled = new LinkedList<Coordinate>();
		Set<Coordinate> settled = new HashSet<Coordinate>();
		unsettled.add(ant);

		while (!unsettled.isEmpty()) {
			Coordinate current = unsettled.poll();

			if (isWater(current) || settled.contains(current))
				continue;

			if (alertZone.contains(current)) {

				AntTile nearbyAnt = getAnt(current);
				boolean isOpposingAnt = nearbyAnt != null
						&& nearbyAnt.getOwner() != ant.getOwner();

				filteredAlertZone.add(current);
				if (isOpposingAnt)
					alertZoneThreat++;

				if (dangerZone.contains(current)) {
					filteredDangerZone.add(current);
					if (isOpposingAnt)
						dangerZoneThreat++;
				}

				unsettled.addAll(getNeighbors(current));

			}

			settled.add(current);
		}

		int alertZoneScent = CombatScentMap.BASE_SOURCE;
		if (alertZoneThreat > 1) {
			alertZoneScent /= alertZoneThreat;
		}

		int dangerZoneScent = CombatScentMap.BASE_SOURCE;
		if (dangerZoneThreat > 1)
			dangerZoneScent /= dangerZoneThreat;

		for (Coordinate c : filteredAlertZone)
			scentMapAlert.setScent(c, scentMapAlert.getScent(c) + alertZoneScent);

		for (Coordinate c : filteredDangerZone)
			scentMapDanger.setScent(c, scentMapDanger.getScent(c) + dangerZoneScent);

		ant.setAlertThreat(alertZoneThreat);
		ant.setDangerThreat(dangerZoneThreat);

	}

	public int getCombatScent(Coordinate c, CombatScent combatScent) {
		return combatScentMaps[combatScent.ordinal()].getScent(c);
	}

	public int getScent(Coordinate c, Scent scent) {
		return scentMaps[scent.ordinal()].getScent(c);
	}

	@Override
	public void initialize(int rows, int cols, int viewradius2, int attackradius2) {
		super.initialize(rows, cols, viewradius2, attackradius2);

		cf = new CoordinateFactory(rows, cols);

		scentMaps = new ScentMap[Scent.values().length];
		for (Scent scent : Scent.values()) {
			int defaultScent = ScentMap.NO_SCENT;
			scentMaps[scent.ordinal()] = new ScentMap(rows, cols, defaultScent);
		}

		combatScentMaps = new CombatScentMap[CombatScent.values().length];
		for (CombatScent combatScent : CombatScent.values()) {
			combatScentMaps[combatScent.ordinal()] = new CombatScentMap();
		}

	}

	public void print() {

		BufferedWriter out = null;
		try {

			String fn = "map.txt";
			out = new BufferedWriter(new FileWriter(fn, false));

			/* print ant locations */
			for (AntTile myAnt : getMyAnts())
				out.write("ant " + myAnt.toString() + "\n");

			/* print ant locations */
			for (AntTile enemyAnt : getEnemyAnts())
				out.write("enemy " + enemyAnt.toString() + "\n");

			/* print scents */
			for (Scent scent : Scent.values()) {
				out.write(scent + "\n");
				ScentMap scentMap = scentMaps[scent.ordinal()];
				for (int row = 0; row < sizeRows; row++) {
					for (int col = 0; col < sizeCols; col++) {
						int value = scentMap.getScent(cf.getCoordinate(row, col));
						out.write(String.format(" %10d", value));
					}
					out.write("\n");
				}
			}

			/* print combat scents */
			for (CombatScent combatScent : CombatScent.values()) {
				out.write(combatScent + "\n");
				CombatScentMap combatScentMap = combatScentMaps[combatScent.ordinal()];
				for (int row = 0; row < sizeRows; row++) {
					for (int col = 0; col < sizeCols; col++) {
						int value = combatScentMap.getScent(cf.getCoordinate(row, col));
						out.write(String.format(" %10d", value));
					}
					out.write("\n");
				}
			}

		} catch (IOException e) {
		} finally {
			if (out != null) {
				try {
					out.flush();
					out.close();
				} catch (IOException e) {
				}
			}
		}

	}

	private void turnScentMapPages() {
		for (Scent scent : Scent.values())
			scentMaps[scent.ordinal()].turnPage();
	}

	@Override
	public void update() {
		super.update();
		diffuseCombatScent();
	}

}
